<?php
namespace tp5_lib\install\logic;

use think\Exception;

/**
 * 模块管理逻辑基类
 * @property string $name 模块名称
 * @property array[] $list 模块列表
 * @property array $setting 设置项
 * @property int $mod 设置文件最后修改时间
 */
class Module
{
    protected $error="";
    public function getError()
    {
        return $this->error;
    }
    static public $statusEnum=[
        "installed"=>"已安装",
        "available"=>"可安装",
        "fragmentary"=>"不完整",
        "forbidden"=>"被禁用",
        "exception"=>"异常",
    ];
    /**
     * @param string $name 模块名称
     * @param array[] $list 模块列表
     */
    public function __construct($name="",$list=[])
    {
        $this->name=$name;
        $this->list=$list;
        // $path = __DIR__ . "/../setting.php";
        // if(!file_exists($path)){
        //     throw new Exception("配置文件丢失");
        // }
        // $this->mod=filemtime($path);
        // $this->setting = require $path;
    }
    public function getList($flush=false)
    {
        if(!$flush && $modules=cache("modules")){
            return $modules;
        }
        $modules=[];
        $template=require __DIR__ . "/../" . config("module.config_dir") . "module.template.php";
        $this->scan("module",function($module,$config)use(&$modules,$template){
            $config = array_merge($template,$config);
            if(empty($config["id"])) $config["id"]=$module;
            $modules[$config["id"]]=$config;
        });

        if(!$config=$this->read("module",false)){
            $config = [
                "forbidden"=>[],
            ];
            $this->write("module",$config);
        }
        
        foreach($modules as &$module){
            try {
                if(in_array($module["id"],$config["forbidden"])){
                    $module["status"]="forbidden";
                    continue;
                }
                /** @var Module $logic */
                $class_name = "\\".$module["logic"];
                $logic=new $class_name($module["id"],$modules);
                $module["status"]=$logic->status();
                if(isset($module["controller"])){
                    $parts=explode("\\",$module["controller"]);
                    $module["path"]=$parts[1] . "/" . str_replace("Controller","",$parts[3]) . "/";
                }
            } catch (\Exception $th) {
                $module["status"]="exception";
            }
        }
        foreach($modules as &$module){
            $module["status_str"]=Module::$statusEnum[$module["status"]];
        }
        uasort($modules,function($a,$b){
            return $a["id"] > $b["id"];
        });
        cache("modules",$modules);
        return $modules;
    }
    /**
     * 获取需要安装的模型列表
     * 默认是从模块设置中读取，也可以手动设置
     */
    protected function getModelList()
    {
        $module=$this->list[$this->name];
        $model_list=[];
        if(!empty($module["model"])){
            foreach($module["model"] as $model_name){
                try {
                    $model_name = "\\".$model_name;
                    $model_list[] = new $model_name;
                } catch (\Exception $th) {
                    $this->error="创建`$model_name`模型失败";
                    return false;
                }
            }
        }
        return $model_list;
    }
    /**
     * 遍历需要安装的字段列表
     * @param callable $cb
     *  function(think\Model $model,string $field_name[,string $attribute,string $default]) : string
     */
    protected function traverseField($cb)
    {
        $module=$this->list[$this->name];
        if(!empty($module["field"])){
            foreach($module["field"] as list($model_name,$field_name,$attribute,$default) ){
                try {
                    $model_name = "\\".$model_name;
                    $model=new $model_name;
                    call_user_func($cb,$model,$field_name,$attribute,$default);
                } catch (\Exception $th) {
                    $this->error="处理`$model_name`.`$field_name`字段失败";
                    return false;
                }
            }
        }
    }
    /**
     * 安装前检查依赖模块
     * @return bool
     */
    protected function checkBeforeInstall()
    {
        $name=$this->name;
        $module_list=$this->list;
        $module=$module_list[$name];

        $need_list=[];
        foreach($module["require"] as $need){
            if(empty($module_list[$need])){
                $this->error="无法找到`$name`模块依赖的`$need`模块";
                return false;
            }
            elseif($module_list[$need]["status"] !== "installed"){
                $need_list[]=$need;
            }
        }
        if($need_list){
            $this->error="`$name`模块依赖未安装的模块:" . implode(",",$need_list) ;
            return false;
        }
        return true;
    }
    /**
     * 安装后更新配置文件
     * @return bool
     */
    protected function updateAfterInstall()
    {
        $name=$this->name;
        $module_list=$this->list;
        $module=$module_list[$name];

        $this->update();
        foreach($module["require"] as $need){
            $class=$module_list[$need]["logic"];
            $require=new $class($need,$module_list);
            $require->update();
        }
        return true;
    }
    /**
     * 卸载后更新配置文件
     * @return bool
     */
    protected function updateAfterUninstall()
    {
        $name=$this->name;
        $module_list=$this->list;
        $module=$module_list[$name];
        foreach($module["require"] as $need){
            $class=$module_list[$need]["logic"];
            $require=new $class($need,$module_list);
            $require->update();
        }
        return true;
    }
    /**
     * 查看状态
     * @return "installed"|"available"|"fragmentary"
     */
    public function status()
    {
        $list=$this->getModelList();
        $installed=0;
        foreach($list as $item){
            $result=$item->db()->query("SHOW TABLE STATUS LIKE '".$item->db()->getTable()."'");
            if($result) $installed++;
        }
        $this->traverseField(function($model,$field_name) use(&$installed){
            $table=$model->db()->getTable();
            if(!$model->db()->query("SHOW TABLE STATUS LIKE '$table'")) return;
            if(!$model->db()->query("SHOW COLUMNS FROM `$table` LIKE '$field_name'")) return;
            $installed++;
        });
        $module=$this->list[$this->name];
        $count = count($list) + count($module["field"]);
        if($count === $installed){
            return "installed";
        }
        elseif(0 === $installed){
            return "available";
        }
        else{
            return "fragmentary";
        }
    }
    /**
     * 安装
     * @return bool
     */
    public function install()
    {
        if(!$this->checkBeforeInstall()) return false;

        //创建数据表
        $model_list=$this->getModelList();
        foreach($model_list as $item){
            $item->install();
        }

        //添加额外的字段
        $this->traverseField(function($model, $field_name, $attribute, $default){
            $table=$model->db()->getTable();
            if(
                $model->db()->query("SHOW TABLE STATUS LIKE '$table'") &&
                !$model->db()->query("SHOW COLUMNS FROM `$table` LIKE '$field_name'")
            ){
                $model->db()->execute("ALTER TABLE `$table` ADD `$field_name` $attribute DEFAULT '$default'");
            }
        });

        $this->list[$this->name]["status"]="installed";
        return $this->updateAfterInstall();
    }
    /**
     * 卸载
     * @return bool
     */
    public function uninstall()
    {
        $model_list=$this->getModelList();
        foreach($model_list as $item){
            $item->db()->execute("DROP TABLE IF EXISTS ".$item->db()->getTable());
        }
        $this->traverseField(function($model, $field_name, $attribute, $default){
            $table=$model->db()->getTable();
            if(
                $model->db()->query("SHOW TABLE STATUS LIKE '$table'") &&
                $model->db()->query("SHOW COLUMNS FROM `$table` LIKE '$field_name'")
            ){
                $model->db()->execute("ALTER TABLE `$table` DROP `$field_name`");
            }
        });
        $this->list[$this->name]["status"]="uninstalled";
        return $this->updateAfterUninstall();
    }
    /**
     * 清理数据
     * @return bool
     */
    public function clear()
    {
        $list=$this->getModelList();
        foreach($list as $item){
            $item->db()->execute("TRUNCATE ".$item->db()->getTable());
        }
        $this->traverseField(function($model, $field_name, $attribute, $default){
            $table=$model->db()->getTable();
            $sql="UPDATE `$table` SET `$field_name`='$default' WHERE 1";
            $model->db()->execute($sql);
        });
        return true;
    }
    /**
     * 更新配置
     * 重新生成配置文件
     * 当前模块或依赖当前模块的模块修改后调用
     * @return bool
     */
    public function update()
    {
        return true;
    }
    /**
     * 禁用模块
     */
    public function forbidden()
    {
        if(!($config=$this->read("module")) || !isset($config["forbidden"])){
            $this->error="配置文件错误";
            return false;
        }
        if(!in_array($this->name,$config["forbidden"])){
            $config["forbidden"][]=$this->name;
            $this->write("module",$config);
        }
        $this->list[$this->name]["status"]="forbidden";
        return $this->updateAfterInstall();
    }
    /**
     * 解除禁用
     */
    public function unforbidden()
    {
        if(!($config=$this->read("module")) || !isset($config["forbidden"])){
            $this->error="配置文件错误";
            return false;
        }
        if(false !== $index = array_search($this->name,$config["forbidden"])){
            unset($config["forbidden"][$index]);
            $this->write("module",$config);
        }
        $this->list[$this->name]["status"]="installed";
        return $this->updateAfterUninstall();
    }
    /**
     * 扫描所有模块下指定的配置文件
     * @param string $name 配置文件名称(不带扩展名)
     * @param callable $cb 回调函数
     *  function cb(string $module,array $config)
     */
    public function scan($name,$cb)
    {
        $app_dir=config("module.app_dir_list");
        $config_dir=config("module.config_dir");
        foreach($app_dir as $_dir){
            $dir="../".$_dir;
            if(!is_dir($dir)){
                $this->error="应用目录设置错误";
                return false;
            }
            if(!$handle=opendir($dir)){
                $this->error="打开应用目录失败";
                return false;
            }
            while($module = readdir($handle)){
                if($module === "." || $module === ".."){
                    continue;
                }
                $path=$dir . DIRECTORY_SEPARATOR . $module;
                if(!is_dir($path)){
                    continue;
                }
                $file=$path . DIRECTORY_SEPARATOR . $config_dir . $name . ".php";
                if(file_exists($file)){
                    $config = include $file;
                    if(!is_array($config)){
                        $content=file_get_contents($file);
                        echo $content;exit;
                        $content=str_replace(["<?php","<?"],"",$content);
                        echo $content;exit;
                        $config=eval($content);
                        if(!is_array($config)){
                            header('Content-Type: text/plain');
                            echo "模块配置文件错误\r\n";
                            echo "path=$file\r\n";
                            exit;
                        }
                    }
                    $config["dir"]=$_dir;
                    call_user_func($cb,$module,$config);
                }
            }
        }
    }
    /**
     * 读取动态配置文件
     * @param string $name 配置文件名称(不带扩展名)
     * @param bool $auto 如果文件不存在，则试图重新生成
     * @return array|false
     */
    public function read($name,$auto=true)
    {
        if(empty($this->_dc)) $dc = $this->_dc = DynamicConfig::alloc("modules");
        else $dc = $this->_dc;
        try {
            return $dc->read($name);
        } catch (\Exception $exce) {
            if($auto && $exce->getCode() === -1){
                $this->list=$this->getList();
                $this->update();
                return $dc->read($name);
            }
            else{
                return false;
            }
        }
    }
    /**
     * 写入动态配置文件
     * @param string $name 配置文件名称(不带扩展名)
     * @param array $config
     */
    public function write($name,$config)
    {
        if(empty($this->_dc)) $dc = $this->_dc = DynamicConfig::alloc("modules");
        else $dc = $this->_dc;
        return $dc->write($name,$config);
    }
    /**
     * 检查密码
     * @param string $password
     * @return bool
     */
    public function password($password)
    {
        // if($this->mod + 8 * 3600 < time()){
        //     throw new Exception("配置文件过期，请手动修改密码");
        // }
        if(config("module.debug_password") !== $password){
            $this->error="密码错误";
            return false;
        }
        return true;
    }
}
